﻿Imports System.Text

Namespace SqlServer
    Public Class TableFactory
        Inherits TableFactoryBase

        ''' <summary>
        ''' 読み込み、生成処理の関数を示します。
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>差し替え可能です。</remarks>
        Public Shared Property Creater As Action(Of Type, Action(Of ColumnDefinitionAttribute)) = AddressOf (New Kairyu.Extensions.DDL.SqlServer.ColumnDefinitionAttributeFactory).Create

        Protected Overrides Sub CreateCore(cn As IDbConnection, ts As IEnumerable(Of Type))
            Dim fn As Func(Of String, InfoLevelEnum, IDbCommand) =
                Function(s, lv)
                    Dim cmd As IDbCommand = cn.CreateCommand
                    cmd.CommandText = s
                    Log.OnExecuteCommand.Invoke(Nothing, New CreateDbCommandEventArgs(cmd) With {.CommandInfoLevel = lv})
                    Return cmd
                End Function

            For Each item In ts
                Dim def As TableDefinition = TableDefinitionReader.Read(item, Creater)
                If def.ColumnDefinisions.Any = False Then Continue For

                fn.Invoke(Me.CreateCommandText(def), InfoLevelEnum.Warn).ExecuteNonQuery()
            Next
        End Sub

        Protected Function CreateCommandText(def As TableDefinition) As String
            Dim sysName As String = If(String.IsNullOrEmpty(def.Table.TableNamespace), "sysobjects", def.Table.TableNamespace & ".sysobjects")

            Dim s As New StringBuilder
            s.AppendFormat("IF EXISTS (SELECT * FROM {0} WHERE name = '{1}' AND xtype = 'U') DROP TABLE {2}", sysName, def.Table.TableName, def.Table.TableFullName)
            s.AppendLine(";")
            s.AppendFormat("CREATE TABLE {0} ({1})", def.Table.TableFullName, CreateColumnDefinitionText(def))
            s.Append(";")

            '#72「DDL処理時、和名を設定しておきたい」
            s.Append(Me.CreateAppendExtendPropertyCommandForTable(def))
            s.Append(Me.CreateAppendExtendPropertyCommandForColumn(def))

            Return s.ToString
        End Function

        Private Function CreateColumnDefinitionText(def As TableDefinition) As String
            Dim s As String = String.Empty
            Dim add As Action(Of String) =
                Sub(text)
                    If String.IsNullOrEmpty(text) Then Return
                    If String.IsNullOrEmpty(s) = False Then s &= ", "
                    s &= text
                End Sub

            '列
            For Each item In def.ColumnDefinisions
                add.Invoke(item.ColumnFullText)
            Next

            '制約
            add.Invoke(Me.CreatePrimaryKeyDefinitionText(def))
            add.Invoke(Me.CreateUniqueDefinitionText(def))

            Return s
        End Function

        Private Function CreatePrimaryKeyDefinitionText(def As TableDefinition) As String
            Dim s As String = String.Empty
            Dim add As Action(Of String) =
                Sub(text)
                    If String.IsNullOrEmpty(s) = False Then s &= ", "
                    s &= text
                End Sub

            For Each item In (From el In def.ColumnDefinisions Where el.Column.IsPrimaryKey Order By el.Index)
                add.Invoke(item.Column.ColumnName)
            Next

            Return If(String.IsNullOrEmpty(s), s, String.Format("CONSTRAINT PK_{0} PRIMARY KEY({1})", def.Table.TableName, s))
        End Function

        Private Function CreateUniqueDefinitionText(def As TableDefinition) As String
            Dim s As String = String.Empty
            Dim add As Action(Of String) =
                Sub(text)
                    If String.IsNullOrEmpty(s) = False Then s &= ", "
                    s &= text
                End Sub

            For Each item In (From el In def.ColumnDefinisions Where el.Column.IsAlternativeLoadKey Order By el.Index)
                add.Invoke(item.Column.ColumnName)
            Next

            Return If(String.IsNullOrEmpty(s), s, String.Format("CONSTRAINT UQ_{0} UNIQUE ({1})", def.Table.TableName, s))
        End Function

        Private Function CreateAppendExtendPropertyCommandForTable(def As TableDefinition) As String
            '#72「DDL処理時、和名を設定しておきたい」
            If def.DisplayAttribute Is Nothing Then Return Nothing

            Dim s As New StringBuilder

            Dim sp = def.Table.TableNamespace
            If String.IsNullOrEmpty(sp) Then sp = "dbo"
            Dim tableArg = String.Format("@level0type=N'SCHEMA', @level0name=N'{0}', @level1type=N'TABLE', @level1name=N'{1}'", sp, def.Table.TableName)

            For Each prop In (From el In def.DisplayAttribute.GetType.GetProperties Where el.CanRead AndAlso el.PropertyType.Equals(GetType(String)))
                Dim val = prop.GetValue(def.DisplayAttribute, Nothing)
                If String.IsNullOrEmpty(val) Then Continue For

                '表示名に限り、親の情報を加える。
                If prop.Name = "Name" AndAlso def.SuperDisplayAttribute IsNot Nothing AndAlso String.IsNullOrEmpty(def.SuperDisplayAttribute.Name) = False Then
                    val = String.Format("{0}.{1}", def.SuperDisplayAttribute.Name, def.DisplayAttribute.Name)
                End If

                s.AppendLine()
                s.AppendFormat("EXEC sys.sp_addextendedproperty @name=N'{0}', @value=N'{1}', {2}", prop.Name, val, tableArg)
                s.Append(";")
            Next

            Return s.ToString
        End Function

        Private Function CreateAppendExtendPropertyCommandForColumn(def As TableDefinition) As String
            '#72「DDL処理時、和名を設定しておきたい」
            Dim s As New StringBuilder

            Dim sp = def.Table.TableNamespace
            If String.IsNullOrEmpty(sp) Then sp = "dbo"
            Dim tableArg = String.Format("@level0type=N'SCHEMA', @level0name=N'{0}', @level1type=N'TABLE', @level1name=N'{1}'", sp, def.Table.TableName)

            For Each item In (From el In def.ColumnDefinisions Where el.DisplayAttribute IsNot Nothing)
                If item.DisplayAttribute Is Nothing Then Continue For

                Dim colArg = String.Format("{0}, @level2type=N'COLUMN', @level2name=N'{1}'", tableArg, item.Column.ColumnName)

                For Each prop In (From el In item.DisplayAttribute.GetType.GetProperties Where el.CanRead AndAlso el.PropertyType.Equals(GetType(String)))
                    Dim val = prop.GetValue(item.DisplayAttribute, Nothing)
                    If String.IsNullOrEmpty(val) Then Continue For

                    '表示名に限り、親の情報を加える。
                    If prop.Name = "Name" AndAlso item.ParentDisplayAttribute IsNot Nothing AndAlso String.IsNullOrEmpty(item.ParentDisplayAttribute.Name) = False Then
                        val = String.Format("{0}.{1}", val, item.ParentDisplayAttribute.Name)
                    End If

                    s.AppendLine()
                    s.AppendFormat("EXEC sys.sp_addextendedproperty @name=N'{0}', @value=N'{1}', {2}", prop.Name, val, colArg)
                    s.Append(";")
                Next
            Next

            Return s.ToString
        End Function
    End Class

End Namespace
